package com.ikingtech.framework.sdk.excel.model;

import com.ikingtech.framework.sdk.context.exception.FrameworkException;
import com.ikingtech.framework.sdk.utils.Tools;
import lombok.Data;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.*;

import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;

/**
 * @author tie yan
 */
@Data
public abstract class WorkSheet<T extends Serializable> {

    private List<TemplateCell> header;

    private List<TemplateCell> template;

    private List<T> data;

    private Boolean settingHeader;

    private Boolean settingTemplate;

    private Map<Integer, Integer> columnWidthMap = new HashMap<>();

    private Map<Integer, XSSFCellStyle> contentCellStyleMap = new HashMap<>();

    protected XSSFWorkbook workbook;

    protected XSSFSheet sheet;

    public static <T extends Serializable> WorkSheet<T> form() {
        WorkSheet<T> sheet = new FormWorkSheet<>();
        sheet.setHeader(new ArrayList<>());
        sheet.setTemplate(new ArrayList<>());
        return sheet;
    }

    public static <T extends Serializable> WorkSheet<T> table() {
        WorkSheet<T> sheet = new TableWorkSheet<>();
        sheet.setHeader(new ArrayList<>());
        sheet.setTemplate(new ArrayList<>());
        return sheet;
    }

    public WorkSheet<T> header() {
        this.settingHeader = true;
        this.settingTemplate = false;
        return this;
    }

    public WorkSheet<T> template() {
        this.settingHeader = false;
        this.settingTemplate = true;
        return this;
    }

    public WorkSheet<T> cell(TemplateCell cell) {
        if (Boolean.TRUE.equals(this.settingHeader)) {
            this.header.add(cell);
        }
        if (Boolean.TRUE.equals(this.settingTemplate)) {
            this.template.add(cell);
        }
        return this;
    }

    public WorkSheet<T> data(List<T> data) {
        this.data = data;
        return this;
    }

    public void write(String sheetName, OutputStream outputStream) {
        this.workbook = new XSSFWorkbook();
        this.sheet = this.workbook.createSheet(sheetName);
        this.writeHeader();
        this.writeContent();
        this.columnWidthMap.forEach((colNo, width) -> this.sheet.setColumnWidth(colNo, width * 256));
        try {
            this.workbook.write(outputStream);
        } catch (IOException e) {
            throw new FrameworkException("write excel exception[{}]", e.getMessage());
        }
    }

    protected abstract void writeContent();

    protected void writeHeader() {
        Map<Integer, List<TemplateCell>> headerMap = Tools.Coll.convertGroup(this.getHeader(), TemplateCell::getStartLine);
        for (Map.Entry<Integer, List<TemplateCell>> entry : headerMap.entrySet()) {
            Row row = this.sheet.createRow(entry.getKey());
            for (TemplateCell templateCell : entry.getValue()) {
                Cell cell = row.createCell(templateCell.getStartColumn());
                cell.setCellValue(templateCell.getContent());
                cell.setCellStyle(this.applyCellStyle(templateCell.getLayout()));
                if (!templateCell.getStartColumn().equals(templateCell.getEndColumn()) || !templateCell.getStartLine().equals(templateCell.getEndLine())) {
                    this.sheet.addMergedRegion(new CellRangeAddress(templateCell.getStartLine(), templateCell.getEndLine(), templateCell.getStartColumn(), templateCell.getEndLine()));
                }
                this.applyColumnWidth(templateCell);
            }
        }
    }

    protected void writeCell(Integer rowNo, List<TemplateCell> templateCells, Map<String, T> cellDataMap) {
        XSSFRow row = this.sheet.createRow(rowNo);
        for (TemplateCell templateCell : templateCells) {
            Cell cell = row.createCell(templateCell.getStartColumn());
            cell.setCellStyle(contentCellStyleMap.computeIfAbsent(templateCell.getStartColumn(), k -> this.applyCellStyle(templateCell.getLayout())));
            this.setCellValue(cell, cellDataMap.get(templateCell.getContent()));
            if (!templateCell.getStartColumn().equals(templateCell.getEndColumn()) || !templateCell.getStartLine().equals(templateCell.getEndLine())) {
                this.sheet.addMergedRegion(new CellRangeAddress(templateCell.getStartLine(), templateCell.getEndLine(), templateCell.getStartColumn(), templateCell.getEndLine()));
            }
            this.applyColumnWidth(templateCell);
        }
    }

    protected void setCellValue(Cell cell, T value) {
        if (value instanceof Double) {
            cell.setCellValue((Double) value);
        } else if (value instanceof Integer) {
            cell.setCellValue((Integer) value);
        } else if (value instanceof String) {
            cell.setCellValue((String) value);
        } else if (value instanceof LocalDateTime) {
            cell.setCellValue((LocalDateTime) value);
        } else if (value instanceof Date) {
            cell.setCellValue((Date) value);
        } else if (value instanceof Boolean) {
            cell.setCellValue((Boolean) value);
        } else if (value instanceof LocalDate) {
            cell.setCellValue((LocalDate) value);
        } else {
            throw new FrameworkException("unsupported cell content type");
        }
    }

    protected XSSFCellStyle applyCellStyle(TemplateCellLayout layout) {
        XSSFCellStyle style = this.workbook.createCellStyle();
        XSSFFont font = this.workbook.createFont();
        font.setFontName(layout.getFontStyle());
        font.setFontHeightInPoints(layout.getFontSize().shortValue());
        font.setBold(Boolean.TRUE.equals(layout.getBold()));
        if (Boolean.TRUE.equals(layout.getUnderline())) {
            font.setUnderline(Font.U_SINGLE);
        }
        font.setItalic(Boolean.TRUE.equals(layout.getItalic()));
        if (null != layout.getFontColour()) {
            font.setColor(new XSSFColor(new java.awt.Color(layout.getFontColour()[0], layout.getFontColour()[1], layout.getFontColour()[2], layout.getFontColour()[3]), new DefaultIndexedColorMap()));
        }
        style.setFont(font);
        if (null != layout.getFontPosition()) {
            if (null != layout.getFontPosition().horizon) {
                style.setAlignment(layout.getFontPosition().horizon);
            }
            if (null != layout.getFontPosition().vertical) {
                style.setVerticalAlignment(layout.getFontPosition().vertical);
            }
        }

        if (null != layout.getBackgroundColour()) {
            style.setFillForegroundColor(new XSSFColor(new java.awt.Color(layout.getBackgroundColour()[0], layout.getBackgroundColour()[1], layout.getBackgroundColour()[2], layout.getBackgroundColour()[3]), new DefaultIndexedColorMap()));
            style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        }

        style.setBorderTop(layout.getBorder().topStyle);
        style.setBorderRight(layout.getBorder().rightStyle);
        style.setBorderLeft(layout.getBorder().leftStyle);
        style.setBorderBottom(layout.getBorder().bottomStyle);

        style.setWrapText(true);
        return style;
    }

    public void applyColumnWidth(TemplateCell templateCell) {
        if (null == templateCell.getWidth()) {
            return;
        }
        if (templateCell.getStartColumn().equals(templateCell.getEndColumn())) {
            this.columnWidthMap.put(templateCell.getStartColumn(), templateCell.getWidth());
        } else {
            for (int i = templateCell.getStartColumn(); i <= templateCell.getEndColumn(); i++) {
                this.columnWidthMap.put(i, templateCell.getWidth());
            }
        }
    }
}
